package frontend.parser.node.stmt;

import frontend.lexer.token.StrConToken;
import frontend.lexer.token.Token;
import frontend.parser.node.Node;
import frontend.parser.node.TokenNode;
import frontend.parser.node.expnode.Cond;
import frontend.parser.node.expnode.Exp;
import frontend.parser.node.expnode.LVal;

import java.util.ArrayList;

public class Stmt extends Node {
    // 语句  Stmt → LVal '=' Exp ';' | [Exp] ';' | Block // h i
    //    | 'if' '(' Cond ')' Stmt [ 'else' Stmt ] // j
    //    | 'for' '('[ForStmt] ';' [Cond] ';' [ForStmt] ')' Stmt
    //    | 'break' ';' | 'continue' ';' // i m
    //    | 'return' [Exp] ';' // f i
    //    | LVal '=' 'getint''('')'';' // h i j
    //    | 'printf''('FormatString{,Exp}')'';' // i j l
    private StmtType stmtType;
    private StmtInf stmtInf;
    public StmtInf setType(StmtType stmtType) {
        this.stmtType = stmtType;
        switch (stmtType) {
            case IF -> {
                return (this.stmtInf = new IfInf());
            }
            case FOR -> {
                return (this.stmtInf = new ForInf());
            }
            case BREAK -> {
                return (this.stmtInf = new BreakInf());
            }
            case CONTINUE -> {
                return (this.stmtInf = new ContinueInf());
            }
            case RETURN -> {
                return (this.stmtInf = new ReturnInf());
            }
            case PRINTF -> {
                return (this.stmtInf = new PrintfInf());
            }
            case GETINT -> {
                return (this.stmtInf = new GetintInf());
            }
            case BLOCK -> {
                return (this.stmtInf = new BlockInf());
            }
            case SEMICN -> {
                return (this.stmtInf = new SemicnInf());
            }
            case ASIGN -> {
                return (this.stmtInf = new AssignInf());
            }
            case EXP -> {
                return (this.stmtInf = new ExpInf());
            }
            default -> {
                return null;
            }
        }
    }
    public StmtType getType() {
        return stmtType;
    }
    public StmtInf getStmtInf() {
        return stmtInf;
    }
    public enum StmtType {
        IF, FOR, BREAK, CONTINUE, RETURN, PRINTF, GETINT, BLOCK, SEMICN, ASIGN, EXP;
    }
    public abstract class StmtInf {
        public abstract StmtType getType();
        public void add(Node node) {
            Stmt.this.add(node);
        }
    }
    public class IfInf extends StmtInf {
        //    | 'if' '(' Cond ')' Stmt [ 'else' Stmt ] // j
        @Override
        public StmtType getType() {
            return StmtType.IF;
        }
        private boolean hasElse = false;
        private Cond cond;
        private Stmt ifStmt;
        private Stmt elseStmt;
        public void addElseTokenNode(TokenNode tokenNode) {
            super.add(tokenNode);
            this.hasElse = true;
        }
        public void addCond(Cond cond) {
            super.add(cond);
            this.cond = cond;
        }
        public void addIfStmt(Stmt stmt) {
            super.add(stmt);
            this.ifStmt = stmt;
        }
        public void addElseStmt(Stmt stmt) {
            super.add(stmt);
            this.elseStmt = stmt;
        }

        public boolean hasElse() {
            return hasElse;
        }

        public Cond getCond() {
            return cond;
        }

        public Stmt getIfStmt() {
            return ifStmt;
        }

        public Stmt getElseStmt() {
            return elseStmt;
        }
    }

    public class ForInf extends StmtInf {
        //    | 'for' '('[ForStmt] ';' [Cond] ';' [ForStmt] ')' Stmt
        @Override
        public StmtType getType() {
            return StmtType.FOR;
        }
        private boolean hasForStmt1 = false;
        private ForStmt forStmt1;
        private boolean hasCond = false;
        private Cond cond;
        private boolean hasForStmt2 = false;
        private ForStmt forStmt2;
        private Stmt stmt;

        public void addForStmt1(ForStmt forStmt1) {
            super.add(forStmt1);
            this.hasForStmt1 = true;
            this.forStmt1 = forStmt1;
        }

        public void addCond(Cond cond) {
            super.add(cond);
            this.hasCond = true;
            this.cond = cond;
        }

        public void addForStmt2(ForStmt forStmt2) {
            super.add(forStmt2);
            this.hasForStmt2 = true;
            this.forStmt2 = forStmt2;
        }

        public void addStmt(Stmt stmt) {
            super.add(stmt);
            this.stmt = stmt;
        }

        public ForStmt getForStmt1() {
            return forStmt1;
        }

        public Cond getCond() {
            return cond;
        }

        public ForStmt getForStmt2() {
            return forStmt2;
        }

        public Stmt getStmt() {
            return stmt;
        }

        public boolean hasForStmt1() {
            return hasForStmt1;
        }

        public boolean hasCond() {
            return hasCond;
        }

        public boolean hasForStmt2() {
            return hasForStmt2;
        }
    }
    public class BreakInf extends StmtInf {

        @Override
        public StmtType getType() {
            return StmtType.BREAK;
        }
        private Token breakToken;
        public void addBreakTokenNode(TokenNode tokenNode) {
            super.add(tokenNode);
            breakToken = tokenNode.getToken();
        }

        public Token getBreakToken() {
            return breakToken;
        }
    }
    public class ContinueInf extends StmtInf {

        @Override
        public StmtType getType() {
            return StmtType.CONTINUE;
        }
        private Token continueToken;
        public void addContinueTokenNode(TokenNode tokenNode) {
            super.add(tokenNode);
            continueToken = tokenNode.getToken();
        }

        public Token getContinueToken() {
            return continueToken;
        }
    }

    public class ReturnInf extends StmtInf {
        //    | 'return' [Exp] ';' // f i
        @Override
        public StmtType getType() {
            return StmtType.RETURN;
        }
        private boolean hasExp = false;
        private Exp exp;
        private TokenNode returnTokenNode;
        public void addExp(Exp exp) {
            super.add(exp);
            hasExp = true;
            this.exp = exp;
        }
        public void addReturnTokenNode(TokenNode tokenNode) {
            super.add(tokenNode);
            this.returnTokenNode = tokenNode;
        }

        public boolean hasExp() {
            return hasExp;
        }

        public Exp getExp() {
            return exp;
        }

        public Token getReturnToken() {
            return returnTokenNode.getToken();
        }
    }
    public class PrintfInf extends StmtInf {
        //    | 'printf''('FormatString{,Exp}')'';' // i j l
        @Override
        public StmtType getType() {
            return StmtType.PRINTF;
        }
        private StrConToken strConToken;
        private final ArrayList<Exp> expList = new ArrayList<>();
        public void addStrConTokenNode(TokenNode tokenNode) {
            super.add(tokenNode);
            strConToken = (StrConToken) tokenNode.getToken();
        }
        public void addExp(Exp exp) {
            super.add(exp);
            expList.add(exp);
        }

        public StrConToken getStrConToken() {
            return strConToken;
        }

        public ArrayList<Exp> getExpList() {
            return expList;
        }
    }
    public class GetintInf extends StmtInf {
        //    | LVal '=' 'getint''('')'';' // h i j
        @Override
        public StmtType getType() {
            return StmtType.GETINT;
        }
        private LVal lVal;
        public void addLVal(LVal lVal) {
            super.add(lVal);
            this.lVal = lVal;
        }

        public LVal getLVal() {
            return lVal;
        }
    }

    public class BlockInf extends StmtInf {

        @Override
        public StmtType getType() {
            return StmtType.BLOCK;
        }
        private Block block;
        public void addBlock(Block block) {
            super.add(block);
            this.block = block;
        }

        public Block getBlock() {
            return block;
        }
    }

    public class SemicnInf extends StmtInf {

        @Override
        public StmtType getType() {
            return StmtType.SEMICN;
        }
    }
    public class AssignInf extends StmtInf {
        // LVal '=' Exp ';'
        @Override
        public StmtType getType() {
            return StmtType.ASIGN;
        }
        private LVal lVal;
        private Exp exp;
        public void addLVal(LVal lVal) {
            super.add(lVal);
            this.lVal = lVal;
        }
        public void addExp(Exp exp) {
            super.add(exp);
            this.exp = exp;
        }

        public LVal getLVal() {
            return lVal;
        }

        public Exp getExp() {
            return exp;
        }
    }
    public class ExpInf extends StmtInf {

        @Override
        public StmtType getType() {
            return StmtType.EXP;
        }
        private Exp exp;
        public void addExp(Exp exp) {
            super.add(exp);
            this.exp = exp;
        }

        public Exp getExp() {
            return exp;
        }
    }

}
